﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Daybreak.Core.Rendering.Materials;

namespace Daybreak.Core
{
    public partial class Scene
    {
        /// <summary>
        /// Generic class for objects which will describe the visual appearance of a geometric object. 
        /// </summary>
        public class Material : IMaterial
        {
            public struct Binding
            {
                public string OldMaterialName;
                public string NewMaterialName;
                public int InputSet;
            }

            #region Fields

            public static readonly Material Debug;

            Shader shader;

            public float? SpecularLevel;
            public float? EmissionLevel;
            public float? ShininessLevel;
            public float? TransparencyLevel;

            public Color? AmbientColor;
            public Color? DiffuseColor;
            public Color? SpecularColor;
            public Color? EmissiveColor;
            public Color? TransparentColor;

            public Image NormalMap;
            public Image DiffuseMap;
            public Image SpecularMap;

            private string _name;
            public string LightingModel;

            public bool DoubleSided;
            public bool Wireframe;

            #endregion

            static Material()
            {
                Debug = new Material("debug_material");
                Debug.DiffuseColor = Color.Red;

                Debug.Wireframe = true;
            }


            #region Constructors
            public Material(string name)
                : this(name, Color.White)
            {
            }

            public Material(string name, Color? diffuse)
            {
                this._name = name;
                this.DiffuseColor = diffuse;
            }
            #endregion

            public override string ToString()
            {
                if (DiffuseMap == null)
                    return string.Format("{0} {1} {2} ", "material:", _name, DiffuseColor.Value.ToString());

                return string.Format("{0} {1} {2} ", "material:", _name, DiffuseMap.File);
            }

            #region Static Methods

            public static BasicShader CreateEffect(Material material)
            {
                if (!Engine.IsReady)
                    return null;

                BasicShader effect = new BasicShader();

                if (material.AmbientColor.HasValue)
                    effect.BasicEffect.AmbientLightColor = material.AmbientColor.Value.ToVector3();

                if (material.DiffuseColor.HasValue)
                    effect.BasicEffect.DiffuseColor = material.DiffuseColor.Value.ToVector3();

                if (material.DiffuseMap != null)
                {
                    effect.BasicEffect.Texture = material.DiffuseMap.Texture as Texture2D;
                    effect.BasicEffect.TextureEnabled = true;
                    //effect.EnableDefaultLighting();

                    Shader.TextureValue tex = effect.FindParam<Shader.TextureValue>(Daybreak.Core.Rendering.Pipeline.SasSemantic.diffuse, 0);

                    if (tex != null)
                        tex.Value = material.DiffuseMap.Texture;


                    if (Engine.Instance.Device.GraphicsDeviceCapabilities.PixelShaderVersion.Major > 1)
                        effect.BasicEffect.PreferPerPixelLighting = true;
                }
                else
                    effect.BasicEffect.TextureEnabled = false;

                effect.BasicEffect.Alpha = material.Opacity;

                if (material.Wireframe)
                    effect.BasicEffect.LightingEnabled = false;

                return effect;
            }

            static Vector3 ToVector(Vector4? vec)
            {
                return new Vector3(vec.Value.X, vec.Value.Y, vec.Value.Z);
            }

            public static bool Compare(Material mat1, Material mat2)
            {
                if (mat1 == null && mat2 == null)
                    return true;

                if (mat1 == null || mat2 == null)
                    return false;

                if (mat1.DiffuseMap != mat2.DiffuseMap)
                    return false;

                if (mat1.NormalMap != mat2.NormalMap)
                    return false;

                if (!Compare<float>(mat1.SpecularLevel, mat2.SpecularLevel))
                    return false;

                if (!Compare<float>(mat1.EmissionLevel, mat2.EmissionLevel))
                    return false;

                if (!Compare<float>(mat1.ShininessLevel, mat2.ShininessLevel))
                    return false;

                if (!Compare<float>(mat1.TransparencyLevel, mat2.TransparencyLevel))
                    return false;

                if (!Compare<Color>(mat1.AmbientColor, mat2.AmbientColor))
                    return false;

                if (!Compare<Color>(mat1.DiffuseColor, mat2.DiffuseColor))
                    return false;

                if (!Compare<Color>(mat1.SpecularColor, mat2.SpecularColor))
                    return false;

                if (!Compare<Color>(mat1.EmissiveColor, mat2.EmissiveColor))
                    return false;

                if (!Compare<Color>(mat1.TransparentColor, mat2.TransparentColor))
                    return false;

                return true; // mat1 equals to mat2

            }

            private static bool Compare<T>(T? val1, T? val2) where T : struct, IEquatable<T>
            {
                if (!val1.HasValue && !val2.HasValue)
                    return true; // both are null

                if (val1.HasValue && val2.HasValue) // compare by value
                    if (val1.Value.Equals(val2.Value))
                        return true;

                return false;
            }

            #endregion

            public float Opacity
            {
                get
                {
                    if (TransparencyLevel.HasValue)
                        return TransparencyLevel.Value;

                    return 1;
                }
            }

            public string Name
            {
                get { return _name; }
            }
        

            public Shader Effect
            {
                get
                {
                    if (shader == null)
                        shader = CreateEffect(this);

                    return shader;
                }
            }

        }

        /// <summary>
        /// Declares a module of Material objects. 
        /// </summary>
        public class MaterialLib
        {
            public List<Material> MaterialList = new List<Material>();

            public MaterialLib() { }

            public Material AddMaterial(string name, Image diffTexture)
            {
                Material mat = FindByName(name, true);
                mat.DiffuseMap = diffTexture;

                return mat;

            }

            public Material FindByName(string name, bool create)
            {
                for (int i = 0; i < MaterialList.Count; i++)
                    if (MaterialList[i].Name == name)
                        return MaterialList[i];

                if (create)
                {
                    Material mat = new Material(name);

                    MaterialList.Add(mat);

                    return mat;
                }

                return null;

            }

        }


    }

    public interface IMaterial
    {
        Shader Effect { get; }
        string Name { get; }
    }
}
